/*
 * Job.java
 */

package model;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;

/**
 * Job represents a job for the Urban Parks program.
 * 
 * @author Carlo Consolacion
 * @author Sean Brown
 * @version 2.0
 */
public class Job implements Cloneable {

	/**
	 * The name/description of this Job.
	 */
	private final String my_name;

	/**
	 * The ID number of this Job.
	 */
	private final int my_id;

	/**
	 * The park ID number for the park that this Job takes place.
	 */
	private final int my_park_id;

	/**
	 * An array of ID values for volunteers (User) that are signed up for this
	 * job in the light workload category.
	 */
	private final int[] my_light_workers;

	/**
	 * An array of ID values for volunteers (User) that are signed up for this
	 * job in the medium workload category.
	 */
	private final int[] my_med_workers;

	/**
	 * An array of ID values for volunteers (User) that are signed up for this
	 * job in the heavy workload category.
	 */
	private final int[] my_heavy_workers;

	/**
	 * The date/time that this Job begins.
	 */
	private final GregorianCalendar my_start;

	/**
	 * 
	 * The date/time that this Job ends.
	 */
	private final GregorianCalendar my_end;

	/**
	 * Default constructor for Job class. Assigns values to all fields.
	 * 
	 * @assumes all values are non null.
	 * @param the_park_id
	 *            The ID of the Park for this Job
	 * @param the_start
	 *            The start date of this Job as a GregorianCalendar.
	 * @param the_end
	 *            The end date of this Job as a GregorianCalendar.
	 * @param the_light
	 *            The array of light volunteer IDs that have signed up for this
	 *            job.
	 * @param the_med
	 *            The array of medium volunteer IDs that have signed up for this
	 *            job.
	 * @param the_heavy
	 *            The array of heavy volunteer IDs that have signed up for this
	 *            job.
	 */
	public Job(final String the_name, final int the_park_id,
			final GregorianCalendar the_start, final GregorianCalendar the_end,
			final int[] the_light, final int[] the_med, final int[] the_heavy) {
		my_name = the_name;
		my_start = (GregorianCalendar) the_start.clone();
		my_end = (GregorianCalendar) the_end.clone();
		my_light_workers = (int[]) the_light.clone();
		my_med_workers = (int[]) the_med.clone();
		my_heavy_workers = (int[]) the_heavy.clone();
		my_park_id = the_park_id;
		my_id = hashCode();
	}

	/**
	 * Overloaded constructor for creating a new Job. Rather than passing the
	 * volunteer IDs, the number of needed volunteers is passed for each
	 * category of workload.
	 * 
	 * @assumes all values are non-null.
	 * @param the_park_id
	 *            The ID of the Park for this Job
	 * @param the_start
	 *            The start date of this Job as a GregorianCalendar.
	 * @param the_end
	 *            The end date of this Job as a GregorianCalendar.
	 * @param the_light_needed
	 *            The number of light Volunteers needed.
	 * @param the_med_needed
	 *            The number of medium Volunteers needed.
	 * @param the_heavy_needed
	 *            The number of heavy Volunteers needed.
	 */
	public Job(final String the_name, final int the_park_id,
			final GregorianCalendar the_start, final GregorianCalendar the_end,
			final int the_light_needed, final int the_med_needed,
			final int the_heavy_needed) {
		this(the_name, the_park_id, the_start, the_end,
				new int[the_light_needed], new int[the_med_needed],
				new int[the_heavy_needed]);
	}

	/**
	 * Returns the start date/time of this Job.
	 * 
	 * @return The date/time that this Jobs begins.
	 */
	public GregorianCalendar getStartTime() {
		return (GregorianCalendar) my_start.clone();
	}

	/**
	 * Returns the end date/time of this Job.
	 * 
	 * @return The date/time that this Jobs ends.
	 */
	public GregorianCalendar getEndTime() {
		return (GregorianCalendar) my_end.clone();
	}

	/**
	 * Returns the ID number of this Job. (Originally generated from hashCode).
	 * 
	 * @return The ID of this Job.
	 */
	public int getID() {
		return my_id;
	}

	/**
	 * Returns the name/description of this Job.
	 * 
	 * @return The name/description of this Job.
	 */
	public String getName() {
		return my_name;
	}

	/**
	 * Returns the ID number of the Park that this job takes place.
	 * 
	 * @return The ID of this Job's Park.
	 */
	public int getParkId() {
		return my_park_id;
	}

	/**
	 * Helper method for finding the number of volunteers signed up in a given
	 * workload category.
	 * 
	 * @param the_workload_array
	 *            The array to search.
	 * @return The number of volunteers signed up for the workload.
	 */
	private int getNumVolunteers(int[] the_workload_array) {
		int my_workers = 0;
		for (int i : the_workload_array) {
			if (i != 0) {
				my_workers++;
			}
		}
		return my_workers;
	}

	/**
	 * Returns the number of heavy workers currently signed up.
	 * 
	 * @return The number of heavy workers currently signed up.
	 */
	public int getNumHeavyVolunteers() {
		return getNumVolunteers(my_heavy_workers);
	}

	/**
	 * Returns the number of medium workers currently signed up.
	 * 
	 * @return The number of medium workers currently signed up.
	 */
	public int getNumMedVolunteers() {
		return getNumVolunteers(my_med_workers);
	}

	/**
	 * Returns the number of light workers currently signed up.
	 * 
	 * @return The number of light workers currently signed up.
	 */
	public int getNumLightVolunteers() {
		return getNumVolunteers(my_light_workers);
	}

	/**
	 * Helper method returns the number of volunteers needed to fill 
	 * a given workload category.
	 * 
	 * @param the_workload
	 *            The workload category to search.
	 * @return The number of volunteers needed for the workload.
	 */
	private int getVolunteersNeeded(int[] the_workload) {
		final int vols = getNumVolunteers(the_workload);
		return the_workload.length - vols;
	}

	/**
	 * Returns the number of light volunteers currently needed.
	 * 
	 * @return The number of light volunteers currently needed.
	 */
	public int getLightVolunteersNeeded() {
		return getVolunteersNeeded(my_light_workers);
	}

	/**
	 * Returns the number of medium volunteers currently needed.
	 * 
	 * @return The number of medium volunteers currently needed.
	 */
	public int getMedVolunteersNeeded() {
		return getVolunteersNeeded(my_med_workers);
	}

	/**
	 * Returns the number of heavy volunteers currently needed.
	 * 
	 * @return The number of heavy volunteers currently needed.
	 */
	public int getHeavyVolunteersNeeded() {
		return getVolunteersNeeded(my_heavy_workers);
	}
	
	/**
	 * Helper method returns the total number of volunteers needed 
	 * in a given workload category.
	 * 
	 * @param the_workload
	 *            The workload category to search.
	 * @return The number of total volunteers needed for the workload.
	 */
	private int getTotalVolunteersNeeded(final int[] the_workload) {
		return the_workload.length;
	}
	
	/**
	 * Returns the total number of light volunteers needed.
	 * 
	 * @return The total number of light volunteers needed.
	 */
	public int getTotalLightVolunteersNeeded() {
		return getTotalVolunteersNeeded(my_light_workers);
	}
	
	/**
	 * Returns the total number of medium volunteers needed.
	 * 
	 * @return The total number of medium volunteers needed.
	 */
	public int getTotalMedVolunteersNeeded() {
		return getTotalVolunteersNeeded(my_med_workers);
	}
	
	/**
	 * Returns the total number of heavy volunteers needed.
	 * 
	 * @return The total number of heavy volunteers needed.
	 */
	public int getTotalHeavyVolunteersNeeded() {
		return getTotalVolunteersNeeded(my_heavy_workers);
	}

	/**
	 * Helper method to get a list of ID values for all volunteers in a given
	 * workload.
	 * 
	 * @param the_workload
	 *            The workload array to search.
	 * @return A list of ID values for volunteers in a given workload category.
	 */
	private List<Integer> getVolunteerIDs(final int[] the_workload) {
		final ArrayList<Integer> temp_list = new ArrayList<Integer>();
		for (int i : the_workload) {
			if (i != 0) {
				temp_list.add((Integer) i);
			}
		}
		return temp_list;
	}

	/**
	 * Returns a list of the ID values of all light Volunteers.
	 * 
	 * @return List of IDs of light users signed up.
	 */
	public List<Integer> getLightVolunteerIDs() {
		return getVolunteerIDs(my_light_workers);
	}

	/**
	 * Returns a list of the ID values of all medium volunteers.
	 * 
	 * @return List of IDs of medium users signed up.
	 */
	public List<Integer> getMediumVolunteerIDs() {
		return getVolunteerIDs(my_med_workers);
	}

	/**
	 * Returns a list of the ID values of all heavy Volunteers.
	 * 
	 * @return List of IDs of heavy users signed up.
	 */
	public List<Integer> getHeavyVolunteerIDs() {
		return getVolunteerIDs(my_heavy_workers);
	}

	/**
	 * Returns a list of all ID values for all volunteers currently signed up.
	 * 
	 * @return List of all volunteer IDs.
	 */
	public List<Integer> getVolunteerIDs() {
		final ArrayList<Integer> temp_list = new ArrayList<Integer>();
		temp_list.addAll(getVolunteerIDs(my_light_workers));
		temp_list.addAll(getVolunteerIDs(my_med_workers));
		temp_list.addAll(getVolunteerIDs(my_heavy_workers));
		return temp_list;
	}

	/**
	 * Adds an ID value to the workload specified. Will not add the ID if the
	 * workload is already full (to protect from ArrayOutOfBounds exception.
	 * 
	 * @param the_id
	 *            The ID of the User to volunteer.
	 * @param the_workload
	 *            The workload category to add to (0 = light, 1 = medium, 2, =
	 *            heavy).
	 */
	public void addVolunteer(final int the_id, final int the_workload) {
		switch (the_workload) {
		case 0: // light
			final int first_empty_light = getFirstEmptySlot(my_light_workers);
			if (first_empty_light != -1) {
				my_light_workers[first_empty_light] = the_id;
			}
			break;
		case 1: // medium
			final int first_empty_med = getFirstEmptySlot(my_med_workers);
			if (first_empty_med != -1) {
				my_med_workers[first_empty_med] = the_id;
			}
			break;
		case 2: // heavy
			final int first_empty_heavy = getFirstEmptySlot(my_heavy_workers);
			if (first_empty_heavy != -1) {
				my_heavy_workers[first_empty_heavy] = the_id;
			}
			break;
		default:
			break;
		}
	}

	/**
	 * Helper method that returns the first empty slot in the workload category
	 * array. Empty slots have a 0 value, as this is how arrays are initialized.
	 * 
	 * @param my_workers
	 *            The workload category to examine.
	 * @return The array position for the first empty slot.
	 */
	private int getFirstEmptySlot(int[] my_workers) {
		int first_empty_slot = 0;
		if (my_workers != null) {
			int i = 0;
			while (i <= my_workers.length - 1) {
				if (my_workers[i] != 0) {
					first_empty_slot++;
				} else {
					break;
				}
				i++;
			}
		}
		// The array index found is invalid because all values are != 0
		if (first_empty_slot == my_workers.length) {
			first_empty_slot = -1;
		}
		return first_empty_slot;
	}

	/**
	 * Overridden toString method. Returns a formatted version of the data in a
	 * Job, including name, park id, start date/time and end date/time (things
	 * that make a Job unique).
	 */
	@Override
	public String toString() {
		final String nl = "\n";
		StringBuilder sb = new StringBuilder();
		sb.append("Job Name: ");
		sb.append(my_name);
		sb.append(nl);
		sb.append("Park ID: ");
		sb.append(my_park_id);
		sb.append(nl);
		sb.append("Start Date/Time: ");
		final DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd hh:mm aaa");
		sb.append(dfm.format(my_start.getTime()));
		sb.append(nl);
		sb.append("End Date/Time: ");
		sb.append(dfm.format(my_end.getTime()));
		return sb.toString();
	}

	/**
	 * Returns a toString for the current state of the volunteers in the
	 * workload categories.
	 * 
	 * @author Sean Brown
	 * @return A String representation of the current volunteers and their
	 *         workload categories info.
	 */
	public String getSlotsToString() {
		final String slash = "/";
		final String nl = "\n";
		final StringBuilder sb = new StringBuilder();
		sb.append("Heavy: ");
		sb.append(getNumHeavyVolunteers());
		sb.append(slash);
		sb.append(my_heavy_workers.length);
		sb.append(nl);
		sb.append("Medium: ");
		sb.append(getNumMedVolunteers());
		sb.append(slash);
		sb.append(my_med_workers.length);
		sb.append(nl);
		sb.append("Light: ");
		sb.append(getNumLightVolunteers());
		sb.append(slash);
		sb.append(my_light_workers.length);
		return sb.toString();
	}

	/**
	 * Overridden hashCode method, simply using toString.
	 */
	@Override
	public int hashCode() {
		return toString().hashCode();
	}

	/**
	 * Overridden equals method, which compares the Job to another Object, and
	 * is equal if the two have the same hashCode.
	 */
	@Override
	public boolean equals(final Object obj) {
		boolean equal = false;
		if (obj != null && getClass() == obj.getClass()) {
			equal = ((Job) obj).hashCode() == hashCode();
		}
		return equal;
	}

	/**
	 * Override of clone method.
	 * 
	 * @author Doug Hutchens
	 */
	@Override
	public Object clone() {
		return new Job(my_name, my_park_id,
				(GregorianCalendar) my_start.clone(),
				(GregorianCalendar) my_end.clone(), my_light_workers.clone(),
				my_med_workers.clone(), my_heavy_workers.clone());
	}

	/**
	 * Static method that converts a date/time object to a String representing
	 * the date (month/day/year).
	 * 
	 * @assumes the_date is non null.
	 * @param the_date
	 *            The date/time to convert.
	 * @return String representation of the date.
	 */
	public static String dateToString(final GregorianCalendar the_date) {
		final SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
		return sdf.format(the_date.getTime());
	}

	/**
	 * Static method that converts a date/time object to a String representing
	 * the time (hour:minute AM/PM).
	 * 
	 * @assumes the_date is non null.
	 * @param the_date
	 *            The date/time to convert.
	 * @return String representation of the time.
	 */
	public static String timeToString(final GregorianCalendar the_date) {
		final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm a");
		return sdf.format(the_date.getTime());
	}
}
